﻿using System.Linq;
using System.Diagnostics;

namespace System.Management.HyperV
{
    /// <summary>
    /// Manages the virtual media (.vhd, .iso, or .vfd files) for a virtual machine.
    /// </summary>
    /// <remarks>http://msdn.microsoft.com/en-us/library/cc136845(VS.85).aspx</remarks>
    public sealed class ImageManagementService : ManagementServiceBase
    {
        public const string WmiClassName = "Msvm_ImageManagementService";

        #region Constructor
        public ImageManagementService()
            : base(ImageManagementService.WmiClassName)
        {
        }
        #endregion

        /// <summary>
        /// Creates a fixed-sized virtual hard disk (.vhd) file.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/cc136780(VS.85).aspx</remarks>
        /// <param name="path">A fully-qualified path that specifies the location of the new file.</param>
        /// <param name="maxInternalSize">The maximum size of the virtual hard disk as viewable by the virtual machine, in bytes. The specified size will be rounded up to the next largest multiple of the sector size.</param>
        public void CreateFixedVirtualHardDisk(string path, ulong maxInternalSize)
        {
            string methodName = "CreateFixedVirtualHardDisk";

            using (var inParams = this.ManagementServiceInstance.GetMethodParameters(methodName))
            {
                DebugDumpProperties(inParams);

                inParams["Path"] = path;
                inParams["MaxInternalSize"] = maxInternalSize;

                InvokeMethod(methodName, inParams, waitForCompleted: true);
            }
        }

        /// <summary>
        /// Creates a differencing virtual hard disk (.vhd) file.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/cc136776(VS.85).aspx</remarks>
        /// <param name="path">A fully-qualified path that specifies the location of the new file.</param>
        /// <param name="parentPath">A fully-qualified path that specifies the location of the parent virtual hard disk file.</param>
        public void CreateDifferencingVirtualHardDisk(string path, string parentPath)
        {
            string methodName = "CreateDifferencingVirtualHardDisk";

            using (var inParams = this.ManagementServiceInstance.GetMethodParameters(methodName))
            {
                DebugDumpProperties(inParams);

                inParams["Path"] = path;
                inParams["ParentPath"] = parentPath;

                InvokeMethod(methodName, inParams, waitForCompleted: true);
            }
        }

        /// <summary>
        /// Creates a dynamically-expanding virtual hard disk (.vhd) file.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/cc136778(VS.85).aspx</remarks>
        /// <param name="path">A fully-qualified path that specifies the location of the new file.</param>
        /// <param name="maxInternalSize">The size of the virtual hard disk, in bytes. If this value is 0, the default size of 3 MB is chosen.</param>
        public void CreateDynamicVirtualHardDisk(string path, ulong maxInternalSize)
        {
            string methodName = "CreateDynamicVirtualHardDisk";

            using (var inParams = this.ManagementServiceInstance.GetMethodParameters(methodName))
            {
                DebugDumpProperties(inParams);

                inParams["Path"] = path;
                inParams["MaxInternalSize"] = maxInternalSize;

                InvokeMethod(methodName, inParams, waitForCompleted: true);
            }
        }

        /// <summary>
        /// Retrieves information about the virtual hard disk files.
        /// </summary>
        /// <remarks>
        /// http://msdn.microsoft.com/en-us/library/cc136797(VS.85).aspx
        /// http://msdn.microsoft.com/en-us/library/cc136935(VS.85).aspx
        /// </remarks>
        /// <param name="path">A fully-qualified path that specifies the location of the disk image file.</param>
        /// <returns>If successful, this object contains the information for the requested virtual hard disk. This is an embedded instance of Msvm_VirtualHardDiskInfo.</returns>
        public VirtualHardDiskInfo GetVirtualHardDiskInfo(string path)
        {
            string methodName = "GetVirtualHardDiskInfo";

            using (var inParams = this.ManagementServiceInstance.GetMethodParameters(methodName))
            {
                DebugDumpProperties(inParams);
                
                inParams["Path"] = path;

                var result = InvokeMethod(methodName, inParams);

                return new VirtualHardDiskInfo(result.OutParameters["Info"].Value.ToString());
            }
        }

        /// <summary>
        /// Mounts a virtual disk image file in loopback mode.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/cc136811(VS.85).aspx</remarks>
        /// <param name="path">A fully-qualified path that specifies the location of the virtual disk image file.</param>
        public void Mount(string path)
        {
            string methodName = "Mount";

            using (var inParams = this.ManagementServiceInstance.GetMethodParameters(methodName))
            {
                DebugDumpProperties(inParams);

                inParams["Path"] = path;

                InvokeMethod(methodName, inParams, waitForCompleted: true);
            }
        }

        /// <summary>
        /// Unmounts the storage image.
        /// This implementation uses the Unmount method of the Msvm_MountedStorageImage class.
        /// </summary>
        /// <remarks>
        /// http://msdn.microsoft.com/en-us/library/cc723872(VS.85).aspx
        /// http://msdn.microsoft.com/en-us/library/cc136982(VS.85).aspx
        /// </remarks>
        public void Unmount(string path)
        {
            string methodName = "Unmount";

            var query = new ObjectQuery("SELECT * FROM Msvm_MountedStorageImage");
            using (var searcher = new ManagementObjectSearcher(this.ManagementServiceInstance.Scope, query))
            {
                var vhdList = from vhd in searcher.Get().OfType<ManagementObject>()
                              where vhd.Properties["Name"].Value.ToString() == path
                              select vhd;

                Debug.Assert(vhdList.Count() == 1, "Searching by VHD path failed to return a unique result.");

                vhdList.First().InvokeMethod(methodName, new object[0]);
            }
        }

        #region Properties
        /// <summary>
        /// A short textual description (one-line string) of the object. This property is inherited from CIM_ManagedElement and it is always set to "Hyper-V Image Management Service".
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/cc136871(VS.85).aspx</remarks>
        public string Caption
        {
            get { return this.ManagementServiceInstance.Properties["Caption"].Value.ToString(); }
        }
        
        /// <summary>
        /// A textual description of the object. This property is inherited from CIM_ManagedElement and it is always set to "Provides Image Management servicing for Hyper-V".
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/cc136871(VS.85).aspx</remarks>
        public string Description
        {
            get { return this.ManagementServiceInstance.Properties["Description"].Value.ToString(); }
        }

        /// <summary>
        /// This property is inherited from CIM_ManagedElement and it is set to "Hyper-V Image Management Service".
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/cc136871(VS.85).aspx</remarks>
        public string ElementName
        {
            get { return this.ManagementServiceInstance.Properties["ElementName"].Value.ToString(); }
        }

        /// <summary>
        /// The date and time the virtual machine configuration was created. This property is inherited from CIM_ManagedSystemElement.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/cc136845(VS.85).aspx</remarks>
        public string InstallDate
        {
            get { return this.ManagementServiceInstance.Properties["InstallDate"].Value.ToString(); }
        }

        /// <summary>
        /// Label by which the object is known. When subclassed, this property can be overridden to be a key property.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/aa387898(VS.85).aspx</remarks>
        public string Name
        {
            get { return this.ManagementServiceInstance.Properties["Name"].Value.ToString(); }
        }

        /// <summary>
        /// The current status of the object. This property is inherited from CIM_ManagedSystemElement and each array element is always set to 2 (OK).
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/cc136845(VS.85).aspx</remarks>
        public ushort[] OperationalStatus
        {
            get { return (ushort[])this.ManagementServiceInstance.Properties["OperationalStatus"].Value; }
        }

        /// <summary>
        /// Strings that describe the various OperationalStatus array values. This property is inherited from CIM_ManagedSystemElement.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/cc136845(VS.85).aspx</remarks>
        public string[] StatusDescriptions
        {
            get { return (string[])this.ManagementServiceInstance.Properties["StatusDescriptions"].Value; }
        }

        /// <summary>
        /// This property is inherited from CIM_ManagedSystemElement but it is not used.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/cc136845(VS.85).aspx</remarks>
        public string Status
        {
            get { return null; }
        }

        /// <summary>
        /// The current health of the element. This attribute expresses the health of this element but not necessarily that of its subcomponents. The possible values are 0 to 30, where 5 means the element is entirely healthy and 30 means the element is completely non-functional. This property is inherited from CIM_ManagedSystemElement and it is always set to 5.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/aa387898(VS.85).aspx</remarks>
        public HealthStateOption HealthState
        {
            get { return (HealthStateOption)this.ManagementServiceInstance.Properties["HealthState"].Value; }
        }

        /// <summary>
        /// Indicates the enabled and disabled states of an element. It can also indicate the transitions between these requested states. For example, shutting down (value=4) and starting (value=10) are transient states between enabled and disabled.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/cc136818(VS.85).aspx</remarks>
        public EnabledStateOption EnabledState
        {
            get { return (EnabledStateOption)this.ManagementServiceInstance.Properties["EnabledState"].Value; }
        }
        
        /// <summary>
        /// The enabled or disabled state of the element when the EnabledState property is set to 1 (Other). This property must be set to null when EnabledState is any value other than 1. This property is inherited from CIM_EnabledLogicalElement and it is always set to NULL.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/cc136845(VS.85).aspx</remarks>
        public string OtherEnabledState
        {
            get { return (string)this.ManagementServiceInstance.Properties["OtherEnabledState"].Value; }
        }

        /// <summary>
        /// The last requested or desired state for the element. The actual state of the element is represented by EnabledState. This property is provided to compare the last requested and current enabled or disabled states. A particular instance of EnabledLogicalElement might not support RequestedStateChange. If this occurs, the value 12 (Not Applicable) is used. This property is inherited from CIM_EnabledLogicalElement and it is always set to 12 (Not Applicable).
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/cc136818(VS.85).aspx</remarks>
        public EnabledStateOption RequestedState
        {
            get { return (EnabledStateOption)this.ManagementServiceInstance.Properties["RequestedState"].Value; }
        }
        
        /// <summary>
        /// An administrator's default or startup configuration for the enabled state of an element. This property is inherited from CIM_EnabledLogicalElement and it is always set to 2 (Enabled).
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/cc136818(VS.85).aspx</remarks>
        public EnabledDefaultOption EnabledDefault
        {
            get { return (EnabledDefaultOption)this.ManagementServiceInstance.Properties["EnabledDefault"].Value; }
        }

        /// <summary>
        /// The date or time when the enabled state of the element last changed. This property is inherited from CIM_EnabledLogicalElement and it is always set to NULL.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/cc136845(VS.85).aspx</remarks>
        public DateTime? TimeOfLastStateChange
        {
            get { return (DateTime?)this.ManagementServiceInstance.Properties["TimeOfLastStateChange"].Value; }
        }

        /// <summary>
        /// The scoping system's creation class name. This property is inherited from CIM_Service and it is set to "Msvm_ComputerSystem".
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/aa388442(VS.85).aspx</remarks>
        public string SystemCreationClassName
        {
            get { return this.ManagementServiceInstance.Properties["SystemCreationClassName"].Value.ToString(); }
        }

        /// <summary>
        /// The name of the hosting computer system. This property is inherited from CIM_Service.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/aa388442(VS.85).aspx</remarks>
        public string SystemName
        {
            get { return this.ManagementServiceInstance.Properties["SystemName"].Value.ToString(); }
        }
        
        /// <summary>
        /// The name of the class or subclass used in the creation of an instance. This property is inherited from CIM_Service and it is set to "Msvm_ImageManagementService".
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/aa388442(VS.85).aspx</remarks>
        public string CreationClassName
        {
            get { return this.ManagementServiceInstance.Properties["CreationClassName"].Value.ToString(); }
        }

        /// <summary>
        /// The name of the primary owner for the service, if one is defined. The primary owner is the initial support contact for the service. This property is inherited from CIM_Service and it is set to "Microsoft".
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/aa388442(VS.85).aspx</remarks>
        public string PrimaryOwnerName
        {
            get { return this.ManagementServiceInstance.Properties["PrimaryOwnerName"].Value.ToString(); }
        }

        /// <summary>
        /// A string that provides information about how the primary owner of the service can be reached (for example, phone number, e-mail address, and so on). This property is inherited from CIM_Service and it is set to "".
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/aa388442(VS.85).aspx</remarks>
        public string PrimaryOwnerContact
        {
            get { return this.ManagementServiceInstance.Properties["PrimaryOwnerContact"].Value.ToString(); }
        }

        /// <summary>
        /// A string value that indicates whether the service is automatically started by a system, an operating system, or is started only upon request. This property is inherited from CIM_Service and it is always set to NULL.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/aa388442(VS.85).aspx</remarks>
        public string StartMode
        {
            get { return (string)this.ManagementServiceInstance.Properties["StartMode"].Value; }
        }
        
        /// <summary>
        /// Indicates whether the service has been started. This property is inherited from CIM_Service and it is set to TRUE.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/aa388442(VS.85).aspx</remarks>
        public bool Started
        {
            get { return bool.Parse(this.ManagementServiceInstance.Properties["Started"].Value.ToString()); }
        }
        #endregion

        #region Not Yet Implemented
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public void ReconnectParentVirtualHardDisk() { throw new NotImplementedException(); }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public void CreateVirtualFloppyDisk() { throw new NotImplementedException(); }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public void MergeVirtualHardDisk() { throw new NotImplementedException(); }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public void CompactVirtualHardDisk() { throw new NotImplementedException(); }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public void ExpandVirtualHardDisk() { throw new NotImplementedException(); }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public void ConvertVirtualHardDisk() { throw new NotImplementedException(); }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public void ValidateVirtualHardDisk() { throw new NotImplementedException(); }
        #endregion

        #region Unsupported
        /// <summary>
        /// This method is not supported.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/cc136845(VS.85).aspx</remarks>
        public void RequestStateChange() { throw new NotSupportedException(); }

        /// <summary>
        /// This method is not supported.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/cc136845(VS.85).aspx</remarks>
        public void StartService() { throw new NotSupportedException(); }

        /// <summary>
        /// This method is not supported.
        /// </summary>
        /// <remarks>http://msdn.microsoft.com/en-us/library/cc136845(VS.85).aspx</remarks>
        public void StopService() { throw new NotSupportedException(); }
        #endregion
    }
}
